﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq.Expressions;
using System.Text;
using FluentExport.CSVExport.Interfaces;
using FluentExport.Extensions;

namespace FluentExport.CSVExport
{
    public class CSVExport : ICSVExport
    {
        #region Members

        private readonly ICSVExportHelper _cvsExportHelper;

        private readonly IDictionary<Type, ICollection<ICSVSource>> _sourceCollection;
        private readonly ICollection<string> _headers;

        #endregion

        #region Constructors

        public CSVExport(ICSVExportHelper cvsExportHelper)
        {
            _sourceCollection = new Dictionary<Type, ICollection<ICSVSource>>();
            _headers = new List<string>();

            _cvsExportHelper = cvsExportHelper;
        }

        #endregion

        #region Factory Method

        public static ICSVExport Init()
        {
            return new CSVExport(new CSVExportHelper());
        }

        #endregion

        #region Methods

        public ICSVExport Header(string header)
        {
            if (header == null)
            {
                throw new InvalidOperationException("The header string can not be null");
            }
            _headers.Add(header);

            return this;
        }

        public ICSVExport Headers(params string[] headers)
        {
            if (headers == null)
            {
                throw new InvalidOperationException("The header list can not be null or empty.");
            }

            foreach (var header in headers)
            {
                _headers.Add(header);
            }

            return this;
        }

        public ICSVExport CreateSource<TSource>(Expression<Func<TSource, object>> source)
        {
            if (source == null)
            {
                throw new ArgumentException("The mapping expression can not be null.");
            }

            var csvSimpleMapper = new CSVSimpleSource(_cvsExportHelper) { PropertyName = _cvsExportHelper.GetPropertyName(source) };
            AddInMapperStorage<TSource>(csvSimpleMapper);

            return this;
        }

        public ICSVExport CreateSource<TSource>(params Expression<Func<TSource, object>>[] sources)
        {
            foreach (var source in sources)
            {
                CreateSource(source);
            }

            return this;
        }

        public ICSVCompoundSource<TSource> CreateCompoundSource<TSource>()
        {
            var complexMapper = new CSVCompoundSource<TSource>(this, _cvsExportHelper);
            AddInMapperStorage<TSource>(complexMapper);

            return complexMapper;
        }

        public CSV Export(object data)
        {
            var type = data.GetType();

            Stream streamResult;

            if (type.IsArray || type.IsGenericCollection())
            {
                streamResult = CreateCVSFromCollection((IEnumerable<object>)data);
            }
            else
            {
                streamResult = CreateCVSFromObject(data);
            }

            return new CSV { Result = streamResult };
        }

        private void AddInMapperStorage<TSource>(ICSVSource csvSource)
        {
            var type = typeof(TSource);

            if (_sourceCollection.ContainsKey(type))
            {
                var sourceList = _sourceCollection[type];
                sourceList.Add(csvSource);
            }
            else
            {
                var sourceList = new List<ICSVSource> { csvSource };
                _sourceCollection.Add(type, sourceList);
            }
        }

        private Stream CreateCVSFromObject(object data)
        {
            var sb = new StringBuilder();

            sb.Append(CreateHeaders());
            sb.Append(CreateCSVRow(data));

            return new MemoryStream(Encoding.ASCII.GetBytes(sb.ToString()));
        }

        private Stream CreateCVSFromCollection(IEnumerable<object> dataCollection)
        {
            var sb = new StringBuilder();

            sb.Append(CreateHeaders());

            foreach (var data in dataCollection)
            {
                var csvRow = CreateCSVRow(data);
                sb.Append(csvRow);
            }

            return new MemoryStream(Encoding.ASCII.GetBytes(sb.ToString()));
        }

        private string CreateHeaders()
        {
            var sb = new StringBuilder();

            foreach (var header in _headers)
            {
                sb.AppendFormat("{0},", _cvsExportHelper.FormatValue(header));
            }

            if (sb.Length > 0)
            {
                sb.AppendLine();
            }

            return sb.ToString();
        }

        private string CreateCSVRow(object data)
        {
            var sources = GetSourcesByType(data.GetType());

            var sb = new StringBuilder();

            foreach (var csvSource in sources)
            {
                var value = csvSource.GetValue(data);
                sb.AppendFormat("{0},", value);
            }

            sb.AppendLine();
            return sb.ToString();
        }

        private IEnumerable<ICSVSource> GetSourcesByType(Type type)
        {
            var sources = _sourceCollection[type];

            if (sources == null)
            {
                throw new InvalidOperationException("There are no configuration for the requested type.");
            }

            return sources;
        }

        #endregion
    }
}
